# -*- coding: utf-8 -*-
"""
Created on Tue Sep 29 14:47:27 2020

@author: Robin
"""
import numpy as np
import pandas as pd
from scipy import stats
from . import utils
from .variable_template import ICAnalysis,IndicatorAnalysis
import re

#from lib import utils
#from lib.variable_template import *

# In[0]: 原有函数

def mean_return_by_quantile(factor_data,
                            by_date=False,
                            by_group=False,
                            demeaned=True,
                            group_adjust=False):
    """
    Computes mean returns for factor quantiles across
    provided forward returns columns.

    Parameters
    ----------
    factor_data : pd.DataFrame - MultiIndex
        A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
        containing the values for a single alpha factor, forward returns for
        each period, the factor quantile/bin that factor value belongs to, and
        (optionally) the group the asset belongs to.
        - See full explanation in utils.get_clean_factor_and_forward_returns
    by_date : bool
        If True, compute quantile bucket returns separately for each date.
    by_group : bool
        If True, compute quantile bucket returns separately for each group.
    demeaned : bool
        Compute demeaned mean returns (long short portfolio)
    group_adjust : bool
        Returns demeaning will occur on the group level.

    Returns
    -------
    mean_ret : pd.DataFrame
        Mean period wise returns by specified factor quantile.
    std_error_ret : pd.DataFrame
        Standard error of returns by specified quantile.
    """

    if group_adjust:
        grouper = [factor_data.index.get_level_values('date')] + ['group']
        factor_data = utils.demean_forward_returns(factor_data, grouper)
    elif demeaned:
        factor_data = utils.demean_forward_returns(factor_data)
    else:
        factor_data = factor_data.copy()

    grouper = ['factor_quantile', factor_data.index.get_level_values('date')]

    if by_group:
        grouper.append('group')

    group_stats = factor_data.groupby(grouper)[
        utils.get_forward_returns_columns(factor_data.columns)] \
        .agg(['mean', 'std', 'count'])

    mean_ret = group_stats.T.xs('mean', level=1).T

    if not by_date:
        grouper = [mean_ret.index.get_level_values('factor_quantile')]
        if by_group:
            grouper.append(mean_ret.index.get_level_values('group'))
        group_stats = mean_ret.groupby(grouper)\
            .agg(['mean', 'std', 'count'])
        mean_ret = group_stats.T.xs('mean', level=1).T

    std_error_ret = group_stats.T.xs('std', level=1).T \
        / np.sqrt(group_stats.T.xs('count', level=1).T)

    return mean_ret, std_error_ret

def factor_weights(factor_data,
                   demeaned = True,
                   group_adjust = False,
                   equal_weight = False,
                   group_weight = False):
    """
    Computes asset weights by factor values and dividing by the sum of their
    absolute value (achieving gross leverage of 1). Positive factor values will
    results in positive weights and negative values in negative weights.
    
    // We can pass self-defined weight_data instead of factor_data, so that we can 
    // weight stocks in different methods.

    Parameters
    ----------
    factor_data : pd.DataFrame - MultiIndex
        A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
        containing the values for a single alpha factor, forward returns for
        each period, the factor quantile/bin that factor value belongs to, and
        (optionally) the group the asset belongs to.
        - See full explanation in utils.get_clean_factor_and_forward_returns
    demeaned : bool
        Should this computation happen on a long short portfolio? if True,
        weights are computed by demeaning factor values and dividing by the sum
        of their absolute value (achieving gross leverage of 1). The sum of
        positive weights will be the same as the negative weights (absolute
        value), suitable for a dollar neutral long-short portfolio
    group_adjust : bool
        Should this computation happen on a group neutral portfolio? If True,
        compute group neutral weights: each group will weight the same and
        if 'demeaned' is enabled the factor values demeaning will occur on the
        group level.
    equal_weight : bool, optional
        if True the assets will be equal-weighted instead of factor-weighted
        If demeaned is True then the factor universe will be split in two
        equal sized groups, top assets with positive weights and bottom assets
        with negative weights
    \\group_weight : dict, optional
    \\    Only when group_adjust is True.

    Returns
    -------
    returns : pd.Series
        Assets weighted by factor value.
    """
    
    def to_weights(group, _demeaned, _equal_weight):

        if _equal_weight:
            group = group.copy()
    
            if _demeaned:
                # top assets positive weights, bottom ones negative
                group = group - group.median()
    
            negative_mask = group < 0
            group[negative_mask] = -1.0
            positive_mask = group > 0
            group[positive_mask] = 1.0
    
            if _demeaned:
                # positive weights must equal negative weights
                if negative_mask.any():
                    group[negative_mask] /= negative_mask.sum()
                if positive_mask.any():
                    group[positive_mask] /= positive_mask.sum()
    
        elif _demeaned:
            group = group - group.mean()
    
        return group / group.abs().sum()
    
    # 分组依据
    grouper = [factor_data.index.get_level_values('date')]
    if group_adjust:
        grouper.append('group')

    # 加权重
    weights = factor_data.groupby(grouper)['factor'] \
        .apply(to_weights, demeaned, equal_weight)
    
    # 组内中性
    if group_adjust:
        weights = weights.groupby(level='date').apply(to_weights, False, False)

    return weights

def factor_information_coefficient(factor_data,
                                   group_adjust=False,
                                   by_group=False):
    """
    Computes the Spearman Rank Correlation based Information Coefficient (IC)
    between factor values and N period forward returns for each period in
    the factor index.

    Parameters
    ----------
    factor_data : pd.DataFrame - MultiIndex
        A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
        containing the values for a single alpha factor, forward returns for
        each period, the factor quantile/bin that factor value belongs to, and
        (optionally) the group the asset belongs to.
        - See full explanation in utils.get_clean_factor_and_forward_returns
    group_adjust : bool
        Demean forward returns by group before computing IC.
    by_group : bool
        If True, compute period wise IC separately for each group.

    Returns
    -------
    ic : pd.DataFrame
        Spearman Rank correlation between factor and
        provided forward returns.
    """

    def src_ic(group):
        f = group['factor']
        _ic = group[utils.get_forward_returns_columns(factor_data.columns)] \
            .apply(lambda x: stats.spearmanr(x, f)[0])
        return _ic

    factor_data = factor_data.copy()

    grouper = [factor_data.index.get_level_values('date')]

    if group_adjust:
        factor_data = utils.demean_forward_returns(factor_data,
                                                   grouper + ['group'])
    if by_group:
        grouper.append('group')

    ic = factor_data.groupby(grouper).apply(src_ic)

    return ic


#换手率计算（从ROWperformance复制）
def quantile_turnover(quantile_factor, quantile, period=1):
    """
    Computes the proportion of names in a factor quantile that were
    not in that quantile in the previous period.

    Parameters
    ----------
    quantile_factor : pd.Series
        DataFrame with date, asset and factor quantile.
    quantile : int
        Quantile on which to perform turnover analysis.
    period: int, optional
        Number of days over which to calculate the turnover.

    Returns
    -------
    quant_turnover : pd.Series
        Period by period turnover for that quantile.
    """

    quant_names = quantile_factor[quantile_factor == quantile]
    quant_name_sets = quant_names.groupby(level=['date']).apply(
        lambda x: set(x.index.get_level_values('asset')))

    name_shifted = quant_name_sets.shift(period)

    new_names = (quant_name_sets - name_shifted).dropna()
    quant_turnover = new_names.apply(
        lambda x: len(x)) / quant_name_sets.apply(lambda x: len(x))
    quant_turnover.name = quantile
    return quant_turnover

# In[1]: 新增函数

def get_indicator_analysis(factor_data,
                           scale_method = 'normal',
                            ic_method = 'rank',                           
                            ret_method = 'cap',
                            turnover_method = 'count',
                            group_num = 5,                           
                            #ind_direction = 'ascending',
                            industry_neu = False,
                            industry_type = 'sw',                           
                            industry_level = 1):
    """
    传入factor_data，以及相关参数，获取单因子分析结果
    """
    # 1. 分析
    ic_decay = None
    ic_series = factor_information_coefficient(factor_data[{'1D','factor'}],group_adjust=False,by_group=False)
    
    ic_stats = pd.DataFrame()
    ic_stats['IC_mean']=ic_series.mean()
    ic_stats['IC_std']=ic_series.std()
    ic_stats['IC_IR']=ic_series.mean()/ic_series.std()
        
    # 2. 将分析结果存储到结构体中
    ic_analysis = ICAnalysis(ic_decay,ic_series,ic_stats)
    
    #return 分析
    return_analysis = pd.DataFrame()
    
    mean_quant_ret, std_quantile = mean_return_by_quantile(factor_data)
    
    if ic_series.mean().values>0:
        return_analysis['mean return G1-G5']=mean_quant_ret.loc[1]-mean_quant_ret.loc[5]
    
    else:
        return_analysis['mean return G1-G5']=mean_quant_ret.loc[5]-mean_quant_ret.loc[1]
    
    
    #turnover 分析
    #input_periods = utils.get_forward_returns_columns(factor_data.columns, require_exact_day_multiple=True).values
 
    def get_valid_columns(columns):
        pattern = re.compile(r"^(\d+([D]))+$", re.IGNORECASE)
        valid_columns = [(pattern.match(col) is not None) for col in columns]
        return columns[valid_columns]       
    input_periods = get_valid_columns(factor_data.columns)    
    turnover_periods = utils.timedelta_strings_to_integers(input_periods)
    quantile_factor = factor_data["factor_quantile"] 
    
    #quantile_turnover={}
    
    quantile_turnover_dict = {
        p: pd.concat(
            [
                quantile_turnover(quantile_factor, q, p)
                for q in quantile_factor.sort_values().unique().tolist()
            ],
            axis=1,
        )
        for p in turnover_periods
    }    
    
    turnover_analysis=pd.DataFrame(index=list(input_periods),columns=['G1','G2','G3','G4','G5'])
    
    for i in range(len(turnover_periods)):
        for j in range(1,6):
            turnover_analysis.loc[input_periods[i]]['G'+str(j)]=quantile_turnover_dict[turnover_periods[i]].mean()[j]
    
    '''
    turnover_analysis返回的是一个DataFrame，格式如下：
               G1        G2        G3        G4        G5
    1D   0.437037  0.662222  0.672593  0.646667  0.433333
    5D   0.757391  0.791739  0.775217      0.77      0.74
    10D  0.849444  0.818333  0.793889  0.794444  0.841111
    
    显示的是每一组的平均换手率
    '''
    
    
    sector_analysis = None      
    indicator_analysis = IndicatorAnalysis(ic_analysis,return_analysis,sector_analysis,turnover_analysis)
 
    return indicator_analysis





















